マルチサイクル RISC-V CPU を作成したい
#自作CPU #RISC-V #Verilog #ULX3S #FPGA #Odeeen #CPU実験
https://gyazo.com/0d9c5448e09d85a03ad2ef2832e0a336
マルチサイクルな 32ビット RISC-V CPU の作成メモ。
以前作ったCPU( https://github.com/thata/R4 )はシングルサイクルなCPUだったが、
シングルサイクルなCPUだと以下のような制限があるため、マルチサイクルなCPUを作ることにする。
プログラムメモリとデータメモリを共通化できない
ブロックRAM / SDRAM の読み込み待ちができない
浮動小数点数の計算待ちができない
リポジトリ
Odeeen
https://github.com/thata/odeeen
MinCamlコンパイラ
MinCamlのプログラムのビルドには、去年RISC-Vへ移植したこちらを利用する。
https://github.com/thata/min-caml/tree/rv32
MinCamlのRISC-Vへの移植についてはこちらを参照
→ MinCamlでRISC-Vのアセンブリコードを出力したい
参考資料
シングルサイクルCPUのマルチサイクル化の手順については、以下が参考になりそう。
POCOのマルチサイクル化
https://www.am.ics.keio.ac.jp/pocobook/multc.pdf
メモリバス
今回のCPUはSDRAMに対応したい。SDRAMのデータの読み書きには複数クロックかかるので、SDRAMの読み書きが終わるまでCPU側が待機する必要がある。
以前作成したCPUのメモリバスは読み書きを待つための信号線を持たなかったので、今回のCPUでは、PicoRV32のメモリバスを参考にVALIDとREADYにより待ち合わせを行うインターフェースを採用する。
BRAMコントローラー
VALIDとREADYによる待ち合わせを行うメモリが必要なので、新たにBRAMコントローラーを作成した。
VALIDとREADYにより待ち合わせを行うBRAMコントローラー
開発環境
例のレイトレの開発環境
開発記録
NOP命令とJAL命令(その1)
JAL命令(その2)
レジスタとADD命令とADDI命令
SW命令
LW命令
BEQ命令
JAL命令とJALR命令
MinCamlを動かすのに必要な命令
Odeeen CPUを実機で動かす
周辺機器とメモリマップ
周辺機器を追加(LEDコントローラ)
JALまわりのバグを修正
周辺機器を追加(UARTコントローラ)
Verilog 上で定義したメモリへのファームウェアの書き込み
putcとgetc
自作CPUへのSDRAMの組み込み
ULX3Sで確保可能なブロックRAMのサイズ
自作CPUをristv-testsでテストしたい(できてない)
足りてない命令を追加(RV32I)
自作CPUでMinCamlを動かしてみる
min_caml_print_int をアセンブリで実装
浮動小数点ユニットの素振り
自作CPUに実装予定の浮動小数点命令
浮動小数点数の比較回路のつくり方
FLW命令とFSW命令
FADD.S命令
FSUB.S命令
FMUL.S命令とFDIV_S命令
FCVT.S.W命令とFCVT.W.S命令
FSGNJ.S命令とFSGNJN.S命令
FMV.X.W命令とFMV.W.X命令
浮動小数点数の比較命令
MinCamlのテストプログラム
乗算と除算を追加したらタイミングエラーが発生した話
レイトレを動かすのに必要なライブラリ関数
sqrt関数
三角関数群の実装
floor関数
abs_float関数
print_byte関数
read_int関数とread_float関数
UARTからの受信を取りこぼしまくる問題
レイトレを動かしてみる
rubyserialでレイトレのデータを受信したい
レイトレが完動して感動した
自作CPU上でレイトレを動かす手順
ステージ分割
1つのブロックRAMに「プログラムメモリ」と「データメモリ」の両方を格納したい。
「命令の取得」と「メモリへのデータの読み書き」を同時に行おうとすると、メモリバスで衝突が発生してしまうため、「命令を取得するステージ(IFステージ)」と「命令を実行するステージ(EXステージ)」とを分けることで、メモリバスの衝突を回避する。
IFステージ(命令フェッチ)
メインメモリ上の pc_reg が指す番地から命令を取得し、instr_reg へ格納
mem_addr に pc_reg の値をセット
pc_reg の更新はここでは行わない
EXステージ(実行)
instr_reg に格納した命令を実行
pc_reg の更新を行う
(追記)その後、データメモリへの読み書きの待ち合わせ用のステージが欲しくなったので、最終的に以下のようにステージ分けをおこなった。ステージの分け方については、デジタル回路設計とコンピュータアーキテクチャ(7章)が参考になった。
IFステージ(命令フェッチ)
EXステージ(実行)
MEMステージ(メモリアクセス)
WBステージ(レジスタ書き戻し、writeback)
https://gyazo.com/20646eff97e8edc11363d174ca80a49e
(さらに追記)その後、FPU導入にともない、以下のようになりました
code:verilog
typedef enum {
IF_STAGE,
EX_STAGE,
FP1_STAGE, // FPU へ入力を渡す
FP2_STAGE, // FPU からの出力を待機
FP3_STAGE, // FPU の出力をレジスタへ保存
MEM_STAGE,
WB_STAGE,
ERR_STAGE
} stage_t;
stage_t stage_reg, stage_next;
RISC-Vの命令セット
RISC-Vの命令セットについては以下が参考になる
https://github.com/jameslzhu/riscv-card/releases/download/latest/riscv-card.pdf
RISC-Vリファレンス
実装予定の命令
当面は使う予定の無い以下の命令を除いた RV32I の命令を実装する
ecall
ebreak
lw 以外のロード系命令
sw 以外のストア系命令
最終的に実装した命令一覧はこちら
→ MinCamlを動かすのに必要な命令
ビット列を浮動小数点数に変換するワンライナー
動作確認用のRubyのワンライナースクリプト。
code:ruby
"0_00000000_00000000000000000000000".gsub("_", "").pack('B*').unpack("g")
#=> 0.0
指数部の最大値と最小値
0xFF
無限大 or NaN
0x00
ゼロ or 非正規数
最大
0xFE (指数 = 127)
仮数 * 170141183460469231731687303715884105728(2 ^ 127)
最小
0x01(指数 = -126)
仮数 * 1.1754943508222875e-38
Tang Nano 9K対応
Tang Nano 9Kで確保可能なブロックRAMのサイズ
Tang Primer 20Kで例のレイトレを動かしたい
発表資料
東大CPU実験のレイトレを自作CPUの上で動かした話